<?php

namespace Core;

use Core\View;
use JsonException;

/**
 * Class Response
 *
 * 封装一个 HTTP 响应。
 * 此类被设计为一个状态对象。它不会立即发送任何内容，
 * 而是收集状态码、头信息和响应体，
 * 直到最后调用 send() 方法。
 *
 * @package Core
 */
class Response
{
    /**
     * HTTP 状态码
     * @var int
     */
    private int $statusCode = 200;

    /**
     * HTTP 响应头
     * @var array<string, string>
     */
    private array $headers = [];

    /**
     * HTTP 响应体内容
     * @var string
     */
    private string $body = '';

    /**
     * 设置一个 HTTP 响应头
     *
     * @param string $key 头名称 (e.g., 'Content-Type')
     * @param string $value 头的值 (e.g., 'application/json')
     * @return static 返回自身以支持链式调用
     */
    public function header(string $key, string $value): static
    {
        // 安全：防止头部注入攻击
        $key = str_replace(["\r", "\n"], '', $key);
        $value = str_replace(["\r", "\n"], '', $value);

        $this->headers[$key] = $value;
        return $this;
    }

    /**
     * 设置 HTTP 状态码
     *
     * @param int $code HTTP 状态码 (e.g., 200, 404, 500)
     * @return static 返回自身以支持链式调用
     */
    public function status(int $code): static
    {
        // 确保状态码在有效范围内
        if ($code >= 100 && $code < 600) {
            $this->statusCode = $code;
        }
        return $this;
    }

    /**
     * 准备一个 JSON 响应
     *
     * @param mixed $data 要编码为 JSON 的数据 (数组或对象)
     * @param int $status (可选) HTTP 状态码, 默认为 200
     * @param array $headers (可选) 额外的 HTTP 响应头
     * @return static 返回自身以支持链式调用
     * @throws JsonException 如果 JSON 编码失败
     */
    public function json(mixed $data, int $status = 200, array $headers = []): static
    {
        $this->status($status);
        $this->header('Content-Type', 'application/json; charset=utf-8');

        foreach ($headers as $key => $value) {
            $this->header($key, $value);
        }

        // 编码 JSON，如果失败则抛出异常
        $this->body = json_encode(
            $data,
            JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR
        );

        return $this;
    }

    /**
     * 准备一个视图 (HTML) 响应
     * 使用输出缓冲来捕获视图的渲染内容。
     *
     * @param string $page 视图文件的路径或名称
     * @param array $data (可选) 传递给视图的数据
     * @return static 返回自身以支持链式调用
     */
    public function view(string $page, array $data = []): static
    {
        // 假设视图默认是 html
        $this->header('Content-Type', 'text/html; charset=utf-8');

        // 安全：移除了 extract($data)。View::render 应该负责处理 $data 数组。
        
        // 使用输出缓冲来捕获 View::render() 的输出
        ob_start();
        try {
            // 假设 View::render 会 echo 内容
            View::render($page, $data); 
        } finally {
            // 获取缓冲区内容并清空
            $this->body = (string) ob_get_clean();
        }

        return $this;
    }

    /**
     * 准备一个纯文本响应
     *
     * @param string $content 文本内容
     * @param int $status (可选) HTTP 状态码, 默认为 200
     * @return static 返回自身以支持链式调用
     */
    public function text(string $content, int $status = 200): static
    {
        $this->status($status);
        $this->header('Content-Type', 'text/plain; charset=utf-8');
        $this->body = $content;
        return $this;
    }

    /**
     * 准备一个重定向响应
     * 注意：此方法不再调用 exit()，而是由 send() 方法处理。
     *
     * @param string $url 目标 URL
     * @param int $code (可选) 重定向状态码 (302=临时, 301=永久), 默认为 302
     * @return static 返回自身以支持链式调用
     */
    public function redirect(string $url, int $code = 302): static
    {
        // 安全：防止头部注入
        $url = str_replace(["\r", "\n"], '', $url);

        $this->status($code);
        $this->header('Location', $url);
        return $this;
    }

    /**
     * 发送最终的 HTTP 响应
     * 此方法应在应用程序的最后调用。
     * 它会发送状态码、所有头信息和响应体。
     *
     * @return void
     */
    public function send(): void
    {
        // 如果头信息已发送，则无法继续
        if (headers_sent()) {
            // 可以在此处记录一个错误
            return;
        }

        // 1. 发送状态码
        http_response_code($this->statusCode);

        // 2. 发送所有头信息
        $isRedirect = false;
        foreach ($this->headers as $key => $value) {
            header("{$key}: {$value}");
            if (strtolower($key) === 'location') {
                $isRedirect = true;
            }
        }

        // 3. 发送响应体 (重定向时通常不发送响应体)
        if (!$isRedirect) {
            echo $this->body;
        }

        // 4. 如果是重定向，脚本应在此处终止
        // (在真实框架中，可能会在此处触发一个 "terminate" 事件，
        // 而不是直接 exit，以便进行清理工作)
        if ($isRedirect) {
            exit;
        }
    }
}